#include <fen.h>

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <position.h>


static const char pcodes[13] = "PpNnBbRrQqKk";
static const int ppieces[12] = {
  PAWN, PAWN, KNIGHT, KNIGHT, BISHOP, BISHOP,
  ROOK, ROOK, QUEEN, QUEEN, KING, KING
};
static int pcolors[12] = {
  WHITE, BLACK, WHITE, BLACK, WHITE, BLACK, 
  WHITE, BLACK, WHITE, BLACK, WHITE, BLACK
};
static const char ccodes[5] = "KQkq";  // castling capabilities
static const unsigned int cvalues[4] = {
  WCASTLE_SHORT, WCASTLE_LONG, BCASTLE_SHORT, BCASTLE_LONG
};


/*
 * Write c in buf[pos], but only if pos < buflen.
 * Returns 1
 */
static int  write_char(char* buf, int buflen, int pos, char c)
{
  if (pos < buflen)
    buf[pos] = c;
  return 1;
}


/* Read the first four fields of FEN/EPD strings.
 *
 * Return 1 in case of success, 0 in case of failure (malformed input
 * string).  In case of success *fen is modified to point to the first
 * character following the data read (which should be a space or
 * '\0').
 */
static int decode_fen_prefix(const char** fen, position_t* pos)
{
  const char* ptr = *fen;

  // 1) Piece placement data.
  for (int rank = 7; rank >= 0; rank--) {
    int file = 0;
    while (file < 8) {
      if (*ptr >= '1' && *ptr <= '8') {
	file += (*ptr - '0');
      } else {
	for (int i = 0; i < 13; i++) {
	  if (*ptr == pcodes[i]) {
	    position_drop_piece(pos,
				SQUARE(file, rank),
				pcolors[i],
				ppieces[i]);
	    ++file;
	    break;
	  }
	  if (i == 13)
	    return 0;
	}
      }
      ++ptr;
    }
    if (file != 8)
      return 0;
    if (*ptr != (rank == 0 ? ' ' : '/'))
      return 0;
    ++ptr;
  }

  // 2) Active color.
  if (*ptr == 'b')
    position_change_stm(pos);
  else if (*ptr != 'w')
    return 0;
  ++ptr;
  if (*ptr != ' ')
    return 0;
  ++ptr;

  // 3) Castling availability.
  int caps = 0;
  if (*ptr == '-') {
    ++ptr;
  } else {
    int expected = 0;
    while (*ptr != ' ') {
      if (expected >= 4) {
	// unexpected character
	return 0;
      }
      if (*ptr == ccodes[expected]) {
	caps |= cvalues[expected];
	++ptr;
      }
      ++expected;
    }
  }
  position_set_castling_caps(pos, caps);
  if (*ptr != ' ')
    return 0;
  ++ptr;

  // 4) En passant target square.
  if (*ptr == '-') {
    ++ptr;
    position_set_ep_square(pos, NO_ENPASSANT);
  } else {
    int file = *ptr - 'a';
    if (file < 0 || file > 7)
      return 0;
    ++ptr;
    int rank = *ptr - '1';
    if (rank != 2 && rank != 5)
      return 0;
    position_set_ep_square(pos, SQUARE(file, rank));
    ++ptr;
  }

  // Commit.
  *fen = ptr;
  return 1;
}


static int encode_fen_prefix(const position_t* pos, char* buf, int buflen)
{
  int n = 0;

  // 1) Piece placement data.
  for (int rank = 7; rank >= 0; rank--) {
    int empty = 0;
    for (int file = 0; file < 8; file++) {
      int p = position_get_square(pos, SQUARE(file, rank));
      if (p == EMPTY) {
	++empty;
      } else {
	if (empty > 0) {
	  n += write_char(buf, buflen, n, '0' + empty);
	  empty = 0;
	}
	int side = position_get_square_side(pos, SQUARE(file, rank));
	for (int i = 0; i < 12; i++) {
	  if (p == ppieces[i] && side == pcolors[i]) {
	    n += write_char(buf, buflen, n, pcodes[i]);
	    break;
	  }
	}
      }
    }
    if (empty > 0)
      n += write_char(buf, buflen, n, '0' + empty);
    n += write_char(buf, buflen, n, (rank == 0 ?  ' ' : '/'));
  }

  // 2) Active color.
  n += write_char(buf, buflen, n, (position_get_stm(pos) == WHITE ?  'w' : 'b'));
  n += write_char(buf, buflen, n, ' ');

  // 3) Castling availability.
  int caps = position_get_castling_caps(pos);
  if (caps == 0) {
    n += write_char(buf, buflen, n, '-');
  } else {
    for (int i = 0; i < 4; i++)
      if (caps & cvalues[i])
	n += write_char(buf, buflen, n, ccodes[i]);
  }
  n += write_char(buf, buflen, n, ' ');

  // 4) En passant target square.
  int ep = position_get_ep_square(pos);
  if (ep == NO_ENPASSANT) {
    n += write_char(buf, buflen, n, '-');
  } else {
    n += write_char(buf, buflen, n, FILE(ep) + 'a');
    n += write_char(buf, buflen, n, RANK(ep) + '1');
  }

  if (buflen > 0)
    buf[(n < buflen) ? n : (buflen - 1)] = 0;
  return n;
}


static const char** decode_epd_operations(const char* epd)
{
  const char** ops = 0;

  // Operations are terminated by non quoted semicolons
  int nops = 0;
  int quote = 0;
  for (const char* c = epd; *c != '\0'; c++) {
    if (*c == '"')
      quote = 1 - quote;
    else if (*c == ';' && !quote)
      ++nops;
  }

  /* Allocate the space for the result: we need two pointers for each
     operations plus one for the null terminator, plus the space for the
     data.*/
  size_t plen = sizeof(const char*) * (2 * nops + 1);
  size_t datalen = strlen(epd) + 1;
  void* mem = malloc(plen + datalen);
  if (mem == 0)
    goto error;
  ops = (const char**)mem;
  char* data = (char*)(mem + plen);

  // Decode the string.
  int op = 0;
  while (*epd != '\0') {
    // operation ::= <space> <opcode>[<space> <arg>];
    if (*epd != ' ')
      goto error;
    ++epd;
    ops[2 * op] = data;

    if (!isalpha(*epd))
      goto error;
    do {
      *data++ = *epd++;
    } while (isalpha(*epd) || isdigit(*epd) || *epd == '_');
    *data++ = '\0';
    if (*epd == ';') {
      // no argument
      ops[2 * op + 1] = 0;
      ++op;
      ++epd;
    } else if (*epd == ' ') {
      ops[2 * op + 1] = data;
      ++op;

      // copy till the next unquoted semicolon
      ++epd;
      quote = 0;
      while (*epd != ';' || quote) {
	if (*epd == '\0')
	  goto error;
	if (*epd == '"')
	  quote = 1 - quote;
	*data++ = *epd++;
      }
      ++epd;
      *data++ = '\0';
    } else {
      goto error;
    }
  }

  ops[2 * nops] = 0;  // sentinel
  *data = '\0';
  return ops;

 error:
  free(ops);
  return 0;
}


int decode_fen(const char* fen, position_t* pos)
{
  assert(pos != 0);

  if (fen == 0)
    return 0;

  position_t p;
  position_clear(&p);

  // 1) Read the first four fields.
  if (!decode_fen_prefix(&fen, &p))
    return 0;
  if (*fen != ' ')
    return 0;
  ++fen;

  // 2) Halfmove clock.
  char* next;
  if (*fen < '0' || *fen > '9')
    return 0;
  position_set_half_moves(&p, strtol(fen, &next, 10));
  fen = next;
  if (*fen != ' ')
    return 0;
  ++fen;

  // 3) Fullmove number.
  if (*fen < '0' || *fen > '9')
    return 0;
  int full = strtol(fen, &next, 10);
  if (full < 1)
    return 0;
  position_set_full_moves(&p, full);
  fen = next;
  if (*fen != '\0')
    return 0;
  ++fen;

  // Commit.
  *pos = p;
  return 1;
}


int encode_fen(const position_t* pos, char* buf, int buflen)
{
  assert(pos != 0);
  assert(buf != 0);
  assert(buflen >= 0);

  // 1) Write the first four fields.
  int n = encode_fen_prefix(pos, buf, buflen);
  n += write_char(buf, buflen, n, ' ');

  // 2) Halfmove clock and fullmove number.
  n += snprintf((n < buflen - 1) ? buf + n : buf,
		(n < buflen - 1) ? buflen - n : 0,
		"%d %d",
		position_get_half_moves(pos),
		position_get_full_moves(pos));
  
  if (buflen > 0)
    buf[(n < buflen) ? n : (buflen - 1)] = '\0';
  return n;
}


int print_fen(FILE* f, const struct position_t* pos)
{
  assert(f != 0);
  assert(pos != 0);

  char dummy = 0;
  char* buf = &dummy;
  int n = encode_fen(pos, buf, 0);
  buf = (char*)malloc((n + 1) * sizeof(char));
  if (buf == 0)
    return 0;
  encode_fen(pos, buf, n + 1);
  fprintf(f, "%s", buf);
  free(buf);
  return 1;
}


const char** decode_epd(const char* epd, struct position_t* pos)
{
  assert(pos != 0);

  if (epd == 0)
    return 0;

  position_t p;
  position_clear(&p);

  // 1) Read the first four fields.
  if (!decode_fen_prefix(&epd, &p))
    return 0;

  // 2) Read operations.
  const char** ops = decode_epd_operations(epd);
  if (ops == 0)
    return 0;

  // 3) Halfmove clock and fullmove number.
  for (const char** o = ops; *o != 0; o += 2) {
    if (!strcmp(*o, "hmvc") || !strcmp(*o, "fmvn")) {
      const char** a = decode_epd_args(*(o + 1));
      if (a != 0 && *a != 0) {
	long n = strtol(*a, 0, 10);
	if (!strcmp(*o, "hmvc") && n >= 0)
	  position_set_half_moves(&p, n);
	else if (n > 0)
	  position_set_full_moves(&p, n);
      }
      free(a);
    }
  }


  // Commit.
  *pos = p;
  return ops;
}


int encode_epd(const struct position_t* pos, char* buf, int buflen, const char** ops)
{
  assert(pos != 0);
  assert(buf != 0);
  assert(buflen >= 0);

  // 1) Write the first four fields.
  int n = encode_fen_prefix(pos, buf, buflen);

  // 2) Operations
  if (ops != 0) {
    while (*ops != 0) {
      n += write_char(buf, buflen, n, ' ');
      n += snprintf((n < buflen - 1) ? buf + n : buf,
		    (n < buflen - 1) ? buflen - n : 0,
		    "%s",
		    *ops);
      ++ops;
      if (*ops != 0) {
	n += write_char(buf, buflen, n, ' ');
	n += snprintf((n < buflen - 1) ? buf + n : buf,
		      (n < buflen - 1) ? buflen - n : 0,
		      "%s",
		      *ops);
      }
      ++ops;
      n += write_char(buf, buflen, n, ';');
    }
  }

  if (buflen > 0)
    buf[(n < buflen) ? n : (buflen - 1)] = '\0';
  return n;
}


const char** decode_epd_args(const char* args)
{
  if (args == 0)
    args = "";

  /* Count the number of non quoted spaces to dermine the number of
     arguments. */
  int quote = 0;
  int nargs = 0;
  if (args[0] != '\0')
    ++nargs;
  for (const char* c = args; *c != '\0'; ++c) {
    if (*c == ' ' && !quote)
      ++nargs;
    else if (*c == '"')
      quote = 1 - quote;
  }

  // Allocate the result.
  void* mem = malloc(sizeof(const char*) * (nargs + 1) + strlen(args) + 1);
  if (mem == 0)
    goto error;

  const char** ret = (const char**)mem;
  char* data = (char*)(mem + sizeof(const char*) * (nargs + 1));

  int a = 0;
  while (*args != '\0') {
    if (a > 0) {
      if (*args != ' ')
	goto error;
      ++args;
    }    
    ret[a++] = data;
    if (*args == '"') {
      ++args;
      while (*args != '\0' && *args != '"')
	*data++ = *args++;
      if (*args != '"')
	goto error;
      ++args;
    } else if (isspace(*args) || *args == '\0') {
      goto error;
    } else {
      while (*args != '\0' && !isspace(*args) && *args != '"')
	*data++ = *args++;
    }
    *data++ = '\0';
  }

  ret[nargs] = 0;
  return ret;

 error:
  free(mem);
  return 0;
}


int print_epd(FILE* f, const struct position_t* pos, const char** ops)
{
  assert(f != 0);
  assert(pos != 0);
  
  int n = encode_epd(pos, 0, 0, ops);
  char* buf = (char*)malloc((n + 1) * sizeof(char));
  if (buf == 0)
    return 0;
  encode_epd(pos, buf, n, ops);
  fprintf(f, "%s", buf);
  free(buf);
  return 1;
}
